﻿using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.Security;
using System.Web.UI;

namespace framework
{
    /// <summary>
    /// Gerenciador de autenticação com o AD
    /// </summary>
    public class LDAP
    {
        private LDAPConfigs _config;
        /// <summary>
        /// Construtor
        /// </summary>
        /// <param name="config">
        /// Configurações para a autenticação<br/>
        /// <b>Exe: "ldap=0.0.0.0;domain=0.0.0.0"</b>
        /// </param>
        public LDAP(LDAPConfigs config)
        {
            _config = config;
        }

        /// <summary>
        /// Valida o usuário no AD
        /// </summary>
        /// <param name="userName">Login</param>
        /// <param name="password">Senha</param>
        /// <returns>Status da autenticação empacotado</returns>
        public AuthenticationStatus validate(string userName, string password)
        {
            string userIdentification = _config.getUserIdentification(userName);
            DirectoryEntry entry = new DirectoryEntry(
                _config.ldapUrl, userIdentification, password
            );
            try
            {
                DirectorySearcher search = new DirectorySearcher(entry);
                search.Filter = "(SAMAccountName=" + userName + ")";
                search.PropertiesToLoad.Add("cn");
                SearchResult result = search.FindOne();
                //...
                string _filtro = (string)result.Properties["cn"][0];
                string[] _grupos = GetGroups(_filtro, result.Path);
                //...
                return new AuthenticationStatus()
                {
                    success = true,
                    message = null,
                    user = new AuthenticationStatus.User() { userName = userName, groups = _grupos },
                };
            }
            catch (COMException ex)
            {
                AuthenticationIndex _indice;
                switch (ex.ErrorCode)
                {
                    case -2147023570: _indice = AuthenticationIndex.PasswordFail; break;
                    case -2147016646: _indice = AuthenticationIndex.ServerError; break;
                    default: _indice = AuthenticationIndex.LDAPException; break;
                }
                return GetError(_indice, userName, ex.Message);
            }
            catch (Exception ex)
            {
                return GetError(AuthenticationIndex.SimpleException, userName, ex.Message);
            }
        }

        private string[] GetGroups(string filterAttribute, string _pathServer)
        {
            DirectorySearcher search = new DirectorySearcher(_pathServer);
            search.Filter = "(cn=" + filterAttribute + ")";
            search.PropertiesToLoad.Add("memberOf");
            List<String> groupNames = new List<string>();

            try
            {
                SearchResult result = search.FindOne();
                int propertyCount = result.Properties["memberOf"].Count;
                string dn;
                int equalsIndex, commaIndex;

                for (int propertyCounter = 0; propertyCounter < propertyCount; propertyCounter++)
                {
                    dn = (string)result.Properties["memberOf"][propertyCounter];
                    equalsIndex = dn.IndexOf("=", 1);
                    commaIndex = dn.IndexOf(",", 1);
                    if (-1 == equalsIndex)
                        return new string[0];
                    groupNames.Add(dn.Substring((equalsIndex + 1), (commaIndex - equalsIndex) - 1));
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Error obtaining group names. " + ex.Message);
            }
            return groupNames.ToArray();
        }
        private AuthenticationStatus GetError(AuthenticationIndex index, string userName, string message)
        {
            return new AuthenticationStatus()
            {
                success = false,
                index = index,
                message = message,
                user = new AuthenticationStatus.User() { userName = userName, groups = new string[0] },
            };
        }
    }

    /// <summary>
    /// Métodos de extenção para Autenticação
    /// </summary>
    public static class LDAPExtension
    {
        /// <summary>
        /// Registra o ticket em cookie (WebForms ou MVC Framework)
        /// </summary>
        /// <param name="page">Página que fazerá o registro</param>
        /// <param name="userName">Usuário autenticado</param>
        /// <param name="userData">Dados adicionais do usuário</param>
        /// <param name="expiration">Quando irá expirar o cookie</param>
        public static void singIn(this Page page, string userName, string userData = null, Nullable<DateTime> expiration = null)
        {
            if (!expiration.HasValue)
                expiration = DateTime.Now.AddDays(1);
            //...
            if (userData == null)
                userData = "";
            //... Monta o ticket
            FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                1,
                userName,
                DateTime.Now,
                expiration.Value,
                true,
                userData
            );
            string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
            //... Adiciona o ticket ao cookie
            HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
            authCookie.Expires = authTicket.Expiration;
            page.Response.Cookies.Add(authCookie);
            //... Redireciona para a próxima pagina(Gerenciado pelo Asp.Net)
            string _url = FormsAuthentication.GetRedirectUrl(userName, false);
            page.Response.Redirect(_url);
        }
        /// <summary>
        /// Desloga o usuário (WebForms ou MVC Framework)
        /// </summary>
        /// <param name="page">Página que fazerá o registro</param>
        public static void singOut(this Page page)
        {
            FormsAuthentication.SignOut();
            //... Destroi o cookie do ticket
            string _nomeCookie = FormsAuthentication.FormsCookieName;
            page.Response.Cookies[_nomeCookie].Value = null;
            //... Limpa o cache
            page.Response.Cache.SetExpires(DateTime.Now.AddDays(-3));
            page.Response.Cache.SetCacheability(HttpCacheability.Private);
            //... Limpa a sessão
            page.Session.Abandon();
            //... Volta par a página de login definida no web.config
            FormsAuthentication.RedirectToLoginPage();
        }
    }
}